package com.elite.ngs.websocket;

import com.elite.ngs.beans.ProjInfo;
import com.elite.ngs.beans.Role;
import com.elite.ngs.beans.Staff;
import com.elite.ngs.event.EventPubSub;
import com.elite.ngs.event.RedisEvent;
import com.elite.ngs.utils.StaffUtils;
import com.wisdge.utils.Assert;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class NgsWebsocketHandler extends TextWebSocketHandler {
    private static final Logger log = LoggerFactory.getLogger(NgsWebsocketHandler.class);

    public static final String STRING_CLIENT_TYPE = "clientType";

    public static final String STRING_AT_ALL = "@ALL@";

    public static final Map<String, Map<String, NgsWebSocketSession>> sessionManager = new ConcurrentHashMap<>();

    @Resource
    private EventPubSub eventPubSub;

    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        Staff staff = (Staff) session.getAttributes().get("staff");
        Assert.isNotNull(staff);
        String clientType = (session.getAttributes().get("mobileType") != null) ? "mobile" : "pc";
        NgsWebSocketSession ngsWebSocketSession = new NgsWebSocketSession(staff, session);
        log.debug("websocket connection established: staff: {}{}{}{}", new Object[]{staff.getId(), Integer.valueOf(session.getLocalAddress().getPort()), session.getRemoteAddress(), clientType});
        saveSession(clientType, ngsWebSocketSession);
    }

    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        String clientType = (session.getAttributes().get("mobileType") != null) ? "mobile" : "pc";
        Staff staff = (Staff) session.getAttributes().get("staff");
        if (staff != null) {
            log.debug("websocket connection closed: staff: {}{} clientType: {}", new Object[]{staff.getId(), Integer.valueOf(status.getCode()), clientType});
            String staffId = staff.getId();
            removeSession(staffId, clientType);
        }
    }

    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        if (message.getPayloadLength() == 0)
            return;
        try {
            if (((String)message.getPayload()).equals("ping")) {
                session.sendMessage((WebSocketMessage)new TextMessage("pong"));
                return;
            }


            Staff staff = null;
            String staffID = "SELITE";
            try {
                staff = (Staff) session.getAttributes().get("staff");
                staffID = staff.getId();
            } catch (Exception e) {
                log.warn("获取消息发送人登录信息异常", e);
            }

            String clientType = (session.getAttributes().get("mobileType") != null) ? "mobile" : "pc";
            String content = (String) message.getPayload();
            log.debug("handleTextMessage{} ,clientType{}, content: {}", new Object[]{staffID, clientType, content});
            RedisEvent redisEvent = new RedisEvent();
            redisEvent.setId("broadcastMessage");
            redisEvent.setValue(content);
            redisEvent.setDate(new Date());
            this.eventPubSub.publishMessage(redisEvent);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public static void sendMessageToSession(WebSocketSession session, NgsWebsocketMessage message) throws IOException {
        log.debug("send message {} to {}", message.getMessage(), message.getTarget());
        session.sendMessage((WebSocketMessage) message.getMessage());
    }

    public static void broadcast(NgsWebsocketMessage message, String clientType) throws IOException {
        broadcast(message, null, clientType);
    }

    public static void broadcast(NgsWebsocketMessage message, Map<String, String> sentStaffMap, String clientType) throws IOException {
        Map<String, Map<String, NgsWebSocketSession>> sessionsDup = new HashMap<>();
        sessionsDup.putAll(sessionManager);
        log.debug("broadcast message{}", clientType);
        if (StringUtils.isNotBlank(clientType)) {
            Map<String, NgsWebSocketSession> webSocketSessionMap = sessionsDup.get(clientType);
            Iterator<Map.Entry<String, NgsWebSocketSession>> iterator = webSocketSessionMap.entrySet().iterator();
            while (iterator.hasNext()) {
                String target = message.getTarget();
                Map.Entry<String, NgsWebSocketSession> next = iterator.next();
                WebSocketSession session = ((NgsWebSocketSession) next.getValue()).getSession();
                if (StringUtils.isBlank(target)) {
                    log.debug("broadcast message to all staff");
                    session.sendMessage((WebSocketMessage) message.getMessage());
                    continue;
                }
                Staff staff = ((NgsWebSocketSession) next.getValue()).getStaff();
                int targetType = message.getTargetType();
                log.debug("broadcast message to group or role{}", Integer.valueOf(targetType));
                ProjInfo project = StaffUtils.getCurProject(staff);
                switch (targetType) {
                    case 1:
                        if (!checkDup(staff.getId(), sentStaffMap) || "@ALL@".equalsIgnoreCase(target))
                            continue;
                        if (project.getGroupId().equals(target)) {
                            log.debug("broadcast message to group{}{}", target, staff.getId());
                            sentStaffMap.put(staff.getId(), "");
                            session.sendMessage((WebSocketMessage) message.getMessage());
                        }
                    case 3:
                        if (checkDup(staff.getId(), sentStaffMap))
                            for (Role role : project.getRoles()) {
                                if (role.getId().equals(target)) {
                                    log.debug("broadcast message to role{}{}", target, staff.getId());
                                    sentStaffMap.put(staff.getId(), "");
                                    session.sendMessage((WebSocketMessage) message.getMessage());
                                }
                            }
                }
            }
        }
    }

    public static boolean checkDup(String staffId, Map<String, String> sentStaffMap) {
        if (sentStaffMap == null)
            return true;
        if (!sentStaffMap.containsKey(staffId))
            return true;
        return false;
    }

    public static void saveSession(String clientType, NgsWebSocketSession ngsWebSocketSession) {
        try {
            String staffId = ngsWebSocketSession.getStaff().getId();
            Map<String, NgsWebSocketSession> sessions = sessionManager.computeIfAbsent(clientType, k -> new HashMap<>());
            sessions.put(staffId, ngsWebSocketSession);
        } catch (Exception e) {
            log.warn("saveSession 操作sessions缓存的动作方法", e);
        }
    }

    public static void removeSession(String staffId, String clientType) {
        Map<String, NgsWebSocketSession> sessions = sessionManager.computeIfAbsent(clientType, k -> new HashMap<>());
        if (sessions.containsKey(staffId))
            sessions.remove(staffId);
    }
}
